﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using BaMusaSoft.RPS.DataModel;
using BaMusaSoft.RPS.DataModel.Repositories;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using BaMusaSoft.RPS.RPSServices;
using System.Windows.Input;
using BaMusaSoft.RPS.ViewModel.HelperClasses;
using BaMusaSoft.RPS.ViewModel.Properties;
using System.Drawing;
using BaMusaSoft.RPS.ViewModel.Infrastructure;
using GalaSoft.MvvmLight.Messaging;
namespace BaMusaSoft.RPS.ViewModel
{
    public class ClaimViewModel : Workspace, IDataErrorInfo
    {
        #region "Fields"
        Claim _claim;
        IUnitOfWork _unitOfWork;
        ClaimsRepository _repository;
        DelegateCommand _addNewCommand;
        DelegateCommand _saveCommand;
        DelegateCommand _printCommand;
        DelegateCommand _findCommand;
        DelegateCommand _deleteClaimDetailCommand;
        //public event EventHandler<NotifyEventArgs<Tuple<Outbox, bool>>> AddOutboxRequest;
        //public event EventHandler<NotifyEventArgs<IList<Contract>>> AddDetailsRequest;
        //public event EventHandler<NotifyEventArgs<Customer>> AddCustomerRequest;

        Dictionary<string, Property> propertiesLookup;
        Dictionary<int, Customer> customersLookup;
        Settings settings;
        ClaimNumber _claimNumber;
        Dictionary<string, string> errors = new Dictionary<string, string>();
        private bool _isNewSchedule;
        private bool _autoIncrementOutbox;

        #endregion
        #region "Consturctor"
        public ClaimViewModel(IUnitOfWork unitOfWork, Claim claim)
        {
            if (unitOfWork == null) throw new ArgumentNullException("unitOfWork");
            if (claim == null) throw new ArgumentNullException("claim");
            _unitOfWork = unitOfWork;
            _repository = _unitOfWork.Claims as ClaimsRepository;
            InitializeViewModel();
            ControlViewModelState(ViewModelStateN.Initialized);
            ControlModelState(ViewModelState.New);
            settings = Settings.Default;
        }
        #endregion

        #region "Binding Properties"
        [Bindable(true)]
        public int Id
        {
            get { return _claim.Id; }
        }
        [Bindable(true)]
        public string CustomerName
        {
            get
            {
                if (_claim.Customer == null) return null;
                return _claim.Customer.Name;
            }
        }
        [Bindable(true)]
        public string OutboxNo
        {
            get
            {
                if (_claim.Outbox == null) return null;
                return _claim.Outbox.OutboxNo;
            }
            set
            {
                if (_claim.Outbox == null) return;
                _claim.Outbox.OutboxNo = value;
                RaisePropertyChanged("OutboxNo");
            }

        }
        [Bindable(true)]
        public string OutboxDate
        {
            get
            {
                if (_claim.Outbox == null) return null;
                return _claim.Outbox.OutboxDate;
            }
            set
            {
                if (_claim.Outbox == null) return;
                _claim.Outbox.OutboxDate = value;
                RaisePropertyChanged("OutboxDate");
            }
        }
        [Bindable(true)]
        public string CaptionText1
        {
            get { return _claim.CaptionText1; }
            set
            {
                _claim.CaptionText1 = value;
                RaisePropertyChanged("CaptionText1");
            }

        }
        [Bindable(true)]
        public string CaptionText2
        {
            get { return _claim.CaptionText2; }
            set
            {
                _claim.CaptionText2 = value;
                RaisePropertyChanged("CaptionText2");
            }
        }
        [Bindable(true)]
        public ObservableCollection<ClaimDetail> ClaimDetails
        {
            get;
            private set;
        }
        [Bindable(true)]
        public ClaimNumber ClaimNo
        {
            get
            {
                return _claimNumber;
            }
            private set
            {
                _claimNumber = value;
                RaisePropertyChanged("ClaimNumber");
            }
        }
        #endregion
        #region "Properties"
        public int SearchClaimNo
        {
            get;
            set;
        }
        [Bindable(true)]
        public bool AutoIncrementOutboxNo
        {
            get { return _autoIncrementOutbox; }

            set
            {
                _autoIncrementOutbox = value;
                RaisePropertyChanged("AutoIncrementOutbox");
            }
        }
        private string CurrentDate { get; set; }
        public bool DeleteIsSelected { get; set; }
        #endregion

        #region "Commands"
        public ICommand AddNewCommand
        {
            get
            {
                if (_addNewCommand == null)
                {
                    _addNewCommand = new DelegateCommand(par => NewClaim());
                }
                return _addNewCommand;
            }
        }
        private void NewClaim()
        {
            //ClaimDetails.CollectionChanged -= OnClaimDetailsChanged;
            _claim = new Claim();
            _claim.Outbox = new Outbox();
            if (AutoIncrementOutboxNo) //If user choose to auto increment outbox number:
            {
                //First get the max of outbox number.
                string maxOutbox = ((OutboxesRepository)_unitOfWork.Outboxes).MaxOutbox;
                //Set it's properties.
                OutboxNo = Helper.GenerateOutboxNo(maxOutbox);
                OutboxDate = CurrentDate;
            }
        }
        public ICommand SaveCommand
        {
            get
            {
                if (_saveCommand == null)
                {
                    _saveCommand = new DelegateCommand(par => Save());
                }
                return _saveCommand;
            }
        }
        private void Save()
        {
            bool isNew = false;
            bool saved = false;
            if (OkSave())
            {
                try
                {
                    if (_isNewSchedule)
                    {
                        _repository.Add(_claim);
                    }
                    _unitOfWork.Save();
                }
                catch
                { 
                
                }
            }
        }
        public ICommand PrintCommand
        {
            get
            {
                if (_printCommand == null)
                {
                    _printCommand = new DelegateCommand(par => Print());
                }
                return _printCommand;
            }
        }
        private void Print()
        {
            ClaimMargins margins = GetMargings();
            PrintableClaim printableClaim = GetPrintableClaim();
            ClaimPrinter printer = new ClaimPrinter(printableClaim, margins);
            printer.Print();

        }

        private PrintableClaim GetPrintableClaim()
        {
            PrintableClaim c = new PrintableClaim();
            c.OutboxNo = this._claim.Outbox.OutboxNo;
            c.OutboxDate = this._claim.Outbox.OutboxDate;
            c.ClaimNo = ClaimNo.Description;
            c.ClaimTotal = 15000;
            c.ClaimTotalInWords = "خمسة عشر الف ريال لاغير";
            c.CustomerName = this._claim.Customer.Name;
            c.Note1 = this._claim.CaptionText1;
            c.Note2 = this._claim.CaptionText2;
            foreach (var detail in this._claim.ClaimDetails)
            {
                PrintableClaimDetails d = new PrintableClaimDetails();
                d.Rent = detail.Rent;
                d.PaidRent = detail.RentPaid;
                d.Property = detail.Property.Description;
                d.Maintenance = detail.Maintenance;
                d.Deposit = detail.Deposit;
                d.Other = detail.Other;
                d.ArrearRent = detail.ArrearRent;
                d.ArrearMaint = detail.ArrearMaintenance;
                d.Total = detail.Total;
                c.Details.Add(d);

            }
            return c;

        }

        private ClaimMargins GetMargings()
        {
            ClaimMargins margins = new ClaimMargins();
            margins.OutboxNoMargins = new PointF(CmToXCoordinate(settings.ClaimForm_OutboxNo_X),
                                                 CmToYCoordinate(settings.ClaimForm_OutboxNo_Y));

            margins.OutboxDateMargins = new PointF(CmToXCoordinate(settings.ClaimForm_OutboxDate_X),
                                                 CmToYCoordinate(settings.ClaimForm_OutboxDate_Y));

            margins.ClaimNoMargins = new PointF(CmToXCoordinate(settings.ClaimForm_ClaimNo_X),
                                                CmToYCoordinate(settings.ClaimForm_ClaimNo_Y));

            margins.CustomerNameMargins = new PointF(CmToXCoordinate(settings.ClaimForm_CustomerName_X),
                                                     CmToYCoordinate(settings.ClaimForm_CustomerName_Y));

            margins.TotalMargins = new PointF(CmToXCoordinate(settings.ClaimForm_ClaimTotal_X),
                                              CmToYCoordinate(settings.ClaimForm_ClaimTotal_Y));

            margins.TotalInWordsMargins = new PointF(CmToXCoordinate(settings.ClaimForm_ClaimTotalInWords_X),
                                                     CmToYCoordinate(settings.ClaimForm_ClaimTotalInWords_Y));

            margins.Note1Margins = new PointF(CmToXCoordinate(settings.ClaimForm_Note1_X),
                                              CmToYCoordinate(settings.ClaimForm_Note1_Y));

            margins.Note2Margins = new PointF(CmToXCoordinate(settings.ClaimForm_Note2_X),
                                              CmToYCoordinate(settings.ClaimForm_Note2_Y));

            margins.DetailRowY = CmToYCoordinate(settings.ClaimForm_DetailRow_Y);
            margins.DetailRentX = CmToXCoordinate(settings.ClaimForm_DetailRent_X);
            margins.DetailRentPaidX = CmToXCoordinate(settings.ClaimForm_DetailRentPaid_X);
            margins.DetailPropertyX = CmToXCoordinate(settings.ClaimForm_DetailProperty_X);
            margins.DetailMaintX = CmToXCoordinate(settings.ClaimForm_DetailMaint_X);
            margins.DetailDepositX = CmToXCoordinate(settings.ClaimForm_DetailDeposit_X);
            margins.DetailOtherX = CmToXCoordinate(settings.ClaimForm_DetailOther_X);
            margins.DetailRentBalanceX = CmToXCoordinate(settings.ClaimForm_DetailRentBalance_X);
            margins.DetailArrearRentX = CmToXCoordinate(settings.ClaimForm_DetailArrearRent_X);
            margins.DetailArrearMaintX = CmToXCoordinate(settings.ClaimForm_DetailArrearMaint_X);
            margins.DetailTotalX = CmToXCoordinate(settings.ClaimForm_DetailTotal_X);
            margins.DetailsGrandTotalMargins = new PointF(CmToXCoordinate(settings.ClaimForm_DetailsGrandTotal_X),
                                                          CmToYCoordinate(settings.ClaimForm_DetailsGrandTotal_Y));
            margins.DetailsRowDistance = CmToYCoordinate(settings.ClaimForm_DetailsRowDistance);
            return margins;
        }
        static float CmToXCoordinate(float cm)
        {
            cm = cm - 0.9f; //necessary for printer hard margins
            return -(cm / 2.54f) * 100;
        }
        static float CmToYCoordinate(float cm)
        {
            //cm = cm - 1f; //necessary for printer hard margins
            return (cm / 2.54f) * 100;
        }

        public ICommand FindCommand
        {
            get
            {
                if (_findCommand == null)
                {
                    _findCommand = new DelegateCommand(par => Find());
                }
                return _findCommand;
            }
        }
        void Find()
        {
            if (SearchClaimNo == 0) return;
            var temp = _repository.GetById(SearchClaimNo);
            if (temp != null)
            {
                this._claim = temp;
                AddExistedDetails(this._claim.ClaimDetails);
                RefreshBinding();
            }
        }

        public ICommand DeleteClaimDetailCommand
        {
            get
            {
                if (_deleteClaimDetailCommand == null)
                {
                    _deleteClaimDetailCommand = new DelegateCommand(DeleteClaimDetail, par => CanDeleteClaimDetail());
                }
                return _deleteClaimDetailCommand;
            }
        }
        void DeleteClaimDetail(object claimDetail)
        {
            ClaimDetail detail = claimDetail as ClaimDetail;
            if (detail != null)
            {
                ClaimDetails.Remove(detail);
                RaisePropertyChanged("ClaimDetails");
            }

        }
        bool CanDeleteClaimDetail()
        {
            return ClaimDetails != null && ClaimDetails.Count > 0 && DeleteIsSelected;
        }
        #endregion

        #region "IDataErrorInfo"

        void SetError(string propertyName, string errorMessage)
        {
            errors[propertyName] = errorMessage;
            RaisePropertyChanged(propertyName);
        }
        void ClearError(string propertyName)
        {
            errors.Remove(propertyName);
        }
        void ClearAllErrors()
        {
            List<string> proeprties = new List<string>();
            foreach (var error in errors)
            {
                proeprties.Add(error.Key);
            }
            errors.Clear();
            foreach (var property in proeprties)
            {
                RaisePropertyChanged(property);
            }
        }
        public string Error
        {
            get { return null; }
        }

        public string this[string columnName]
        {
            get
            {
                if (errors.ContainsKey(columnName))
                {
                    return errors[columnName];
                }
                return string.Empty;
            }
        }
        #endregion

        #region "Private methods"
        private Dictionary<string, Property> FillPropertiesLookup()
        {
            var properties = _unitOfWork.Properties.GetAll();
            return properties.ToDictionary(p => p.PropertyNo);
        }
        private Dictionary<int, Customer> FillCustomersLookup()
        {
            var customers = _unitOfWork.Customers.GetAll();
            return customers.ToDictionary(cust => cust.CustomerId);
        }
        private void InitializeViewModel()
        {
            //propertiesLookup = FillPropertiesLookup();
            //customersLookup = FillCustomersLookup();
        }
        private void SyncDetails(IEnumerable<ClaimDetail> details, ViewModelState state)
        {
            if (ClaimDetails != null && state != ViewModelState.Saved)
                ClaimDetails.CollectionChanged -= OnClaimDetailsChanged;
            switch (state)
            {
                case ViewModelState.New:
                    ClearDetails();
                    ClaimDetails = new ObservableCollection<ClaimDetail>(details);
                    ClaimDetails.CollectionChanged += OnClaimDetailsChanged;
                    break;
                case ViewModelState.Saved:
                    //Do nothing
                    break;
                case ViewModelState.Deleted:
                    ClearDetails();
                    ClaimDetails = new ObservableCollection<ClaimDetail>(details);
                    ClaimDetails.CollectionChanged += OnClaimDetailsChanged;
                    break;
                case ViewModelState.Loaded:
                    ClaimDetails = new ObservableCollection<ClaimDetail>(details);
                    ClaimDetails.CollectionChanged += OnClaimDetailsChanged;
                    AddExistedDetails(details);
                    break;
                default:
                    break;
            }
            //this.ClaimDetails = new ObservableCollection<ClaimDetail>();
            //if (_claim.ClaimDetails.Count > 0)
            //{
            //    AddExistedDetails(_claim.ClaimDetails);
            //}
            ////ToDo: Unsubscribe from this event when exit and when create new Claim
            //this.ClaimDetails.CollectionChanged += OnClaimDetailsChanged;
        }
        private void ClearDetails()
        {
            if (ClaimDetails == null) return;
            foreach (var claimDetail in ClaimDetails)
            {
                //ToDo: If you implement PropertyChanged in ClaimDetail then unsubscirbe from it here.
            }
            ClaimDetails.Clear();
        }
        private void AddExistedDetails(IEnumerable<ClaimDetail> details)
        {
            foreach (var detail in details)
            {
                this.ClaimDetails.Add(detail);
            }
        }
        private void OnClaimDetailsChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    foreach (var item in e.NewItems)
                    {
                        ClaimDetail detail = item as ClaimDetail;
                        if (detail != null)
                        {
                            _claim.ClaimDetails.Add(detail);
                        }
                    }
                    break;
                case NotifyCollectionChangedAction.Move:
                    break;
                case NotifyCollectionChangedAction.Remove:
                    foreach (var item in e.OldItems)
                    {
                        ClaimDetail detail = item as ClaimDetail;
                        if (detail != null)
                        {
                            _claim.ClaimDetails.Remove(detail);
                        }
                    }
                    break;
                case NotifyCollectionChangedAction.Replace:
                    break;
                case NotifyCollectionChangedAction.Reset:
                    break;
                default:
                    break;
            }
        }
        public void SetCustomer(Customer customer)
        {
            if (customer == null) throw new ArgumentNullException("customer");
            //ClaimNo = GetCustomerClaimNumber(customer);
            _claim.Customer = customer;
            RaisePropertyChanged("CustomerName");
        }

        private ClaimNumber GetCustomerClaimNumber(Customer customer, out bool inRange)
        {
            //This method will check if the customer had any claims befor, then will determine
            //What the current claim number this customer should get.
            //is the claim nunber is in the range( 1 -2 -3 - Last) then inRange parameter
            //will set to true. otherwise the customer has got all the claims, so inRange will
            //set to false and calim number will defaulted to LAST. This will let the uset know
            //that the customer exceed the allowed claims but will not prevent him from creating
            //default LAST calim.
            var customerClaims = _unitOfWork.ClaimNumbers.Query(c => c.Customer.CustomerId == customer.CustomerId).ToList();
            ClaimNumber cn = new ClaimNumber();
            cn.Customer = customer;
            switch (customerClaims.Count)
            {
                case 0:
                    //First Claim
                    cn.Description = Resources.FirstClaim;
                    inRange = true;
                    break;
                case 1:
                    //Second Claim
                    cn.Description = Resources.SecondClaim;
                    inRange = true;
                    break;
                case 2:
                    //Third Claim
                    cn.Description = Resources.ThirdClaim;
                    inRange = true;
                    break;
                case 3:
                    //Last Claim
                    cn.Description = Resources.LastClaim;
                    inRange = true;
                    break;


                default:
                    cn.Description = Resources.LastClaim;
                    inRange = false;
                    break;
            }
            return cn;
        }
        public Customer ClaimedCustomer
        {

            get { return _claim.Customer; }
        }
        public void SetOutbox(Tuple<Outbox, bool> data)
        {
            if (data == null) throw new ArgumentNullException("data");

            _claim.Outbox = data.Item1;
            this.AutoIncrementOutboxNo = data.Item2;
            this.CurrentDate = data.Item1.OutboxDate;
        }

        public void SetClaimDetails(IList<Contract> list)
        {
            if (list != null && list.Count > 0)
            {
                foreach (var contract in list)
                {
                    //First check that this Contract had not been added befor. If so continue to the next.
                    if (this.ClaimDetails.Any(x => x.ContractNo == contract.ContractNo)) continue;
                    ClaimDetail detail = new ClaimDetail();
                    detail.ClaimId = _claim.Id;
                    detail.ContractNo = contract.ContractNo;
                    detail.Rent = contract.AgreedRent;
                    detail.RentPaid = contract.AgreedRent - contract.RentBalance;
                    detail.Maintenance = contract.MaintenanceBalance;
                    detail.Deposit = contract.DepositBalance;
                    detail.Other = 0;
                    detail.RentBalance = contract.RentBalance;
                    detail.ArrearRent = 0;
                    detail.ArrearMaintenance = 0;
                    detail.Property = contract.Property;
                    detail.Total = (detail.RentBalance + detail.Maintenance + detail.Deposit + detail.Other + detail.ArrearRent + detail.ArrearMaintenance);
                    this.ClaimDetails.Add(detail);
                }
                RaisePropertyChanged("ClaimDetails");

            }
        }

        private void ControlModelState(ViewModelState state)
        {
            switch (state)
            {
                case ViewModelState.New:
                    NewClaim();
                    _isNewSchedule = true;
                    break;
                case ViewModelState.Saved:
                    _isNewSchedule = false;

                    break;
                case ViewModelState.Deleted:
                    _claim = new Claim();
                    _isNewSchedule = true;
                    break;
                case ViewModelState.Loaded:
                    _isNewSchedule = false;

                    break;
                default:
                    break;
            }
            SyncDetails(_claim.ClaimDetails, state);
            Messenger.Default.Send(state, MessageToken);
            RefreshBinding();
        }
        #endregion
        #region "Helper methods"
        private void RefreshBinding()
        {
            //Query expression 
            var bindableProperties = from p in typeof(ClaimViewModel).GetProperties()
                                     where p.GetCustomAttributes(true).Cast<Attribute>().Contains(new BindableAttribute(true))
                                     select p;
            foreach (var p in bindableProperties)
            {
                RaisePropertyChanged(p.Name);
            }
        }
        private bool OkSave()
        {
            ClearAllErrors();
            bool okSave = true;
            StringBuilder errorMessage = null;
            try
            {
                if (_claim == null)
                {
                    throw new ArgumentNullException("claim");
                }

                var validationResults = DomainValidator.Validate<Claim>(_claim);
                //var policyResults = BusinessPolicies.ApplySignerPolicy(Signer);
                if (!validationResults.IsValid)
                {
                    okSave = false;
                    short index = 0;
                    foreach (var result in validationResults)
                    {
                        //SetError(result.Key, result.Message);
                        if (errorMessage == null) errorMessage = new StringBuilder();
                        index++;
                        errorMessage.AppendFormat("{0}{1}{2}{3}{4} ",
                              index,
                              "-",
                              "\t",
                              result.Message,
                              "\n"
                              );

                    }
                    Messenger.Default.Send(errorMessage.ToString(), MessageToken);
                }
                return okSave;
            }
            catch (Exception)
            {

                throw;
            }
        }
        #endregion

        #region "Base"
        public override void Cleanup()
        {
            ClaimDetails.CollectionChanged -= OnClaimDetailsChanged;
            _unitOfWork.Dispose();
            base.Cleanup();

        }


        public override string DisplayName
        {
            get;

            protected set;
        }

        public override bool HasChanges
        {
            get { return _unitOfWork.HasChanges; }
        }
        #endregion

        void ControlViewModelState(ViewModelStateN state)
        {
            switch (state)
            {
                case ViewModelStateN.Initialized:

                    break;
                case ViewModelStateN.ReadyForEdit:
                    break;
                default:
                    break;
            }
        }

        public IUnitOfWork UnitOfWork { get { return _unitOfWork; } }
    }
}
